相信大家都知道 Javascript
是一個動態語言,也就是說瀏覽器在執行的時候其實會需要多一個步驟去將程式碼轉譯為機器可以讀懂的行為,而這個轉譯也會依據不一樣的瀏覽器有不一樣的行為 ( 像大家最常聽到的應該就是 Chrome V8
),相較於其他靜態語言如 C
是在執行前就已經被編譯好,動態語言是在執行的時候在去做解析,也因為如此效能上通常會比不上已經預先編譯好的程式碼。也因此大量的複雜計算一直不是 Javascript
的強項,於是為了達成 Web 統一天下的夢想, WebAssembly 被發明了出來
WebAssembly
是一種標準,通常是被其他語言當作轉譯的目標如 C
、Rust
、 Go
,將程式碼轉成 bytecode
來執行,而因為省略了上面動態編譯的部分,所以可以帶來接近原生的速度。
而這次會從 Rust
開始試著編譯成 WebAssembly
,原因有幾個
WebAssembly
的生態蠻豐富的, wasm-bindgen 對 Javascript
的一些互動都有做基本的包裝Rust
的爸爸是 Mozilla
,同時也是 WebAssembly
的大力推動者,跟著走準沒錯!那我們接下來就開始來進行吧,首先要先安裝 Rust
請參考 官網完成,裝完之後應該也會順便完成安裝 Cargo
,Rust
跟 Cargo
的關係就像 Node
跟 Npm
的關係一樣,是負責套件管理使用,接下來就可以來實際創建我們的專案囉,在原本的架構裡面新增 wasm
的資料夾,架構如下
Cargo.toml
跟 Cargo.lock
就像是 package.json
跟 package-lock.json
,控制了要安裝哪些套件及版本,而 pkg
則是最後產出的檔案,src
則是程式碼。最後的 toml
檔應該會長這樣
[package]
name = "wasm"
version = "0.1.0"
authors = ["xxxxx"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
console_error_panic_hook = { version = "0.1.1", optional = true }
wee_alloc = { version = "0.4.2", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.2"
[profile.release]
# 下面的設定會跟最後的效能有關係
opt-level = 3
lto = true
panic = 'abort'
主要就是使用了 wasm-bindgen
,接著在創建一個 lib.rs
,寫一個簡單的費式數列,跟 Javascript
不一樣的是 Rust
對於傳入傳出的型態都會嚴格定義,而 match
的用法就是 switch
一樣,就這樣完成了我們的第一個函數
// 引入模組
extern crate wasm_bindgen;
#[wasm_bindgen]
pub fn fib(i: u32) -> u32 {
match i {
0 => 0,
1 => 1,
_ => fib(i-1) + fib(i-2)
}
}
接著需要設定一下 Webpack
,在 config
的 plugins
加入這段,forceMode
會讓 Rust
產出 production
或著 dev
的版本,在最後要實際使用前記得更換,不然會發生效能差異很大。
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, './wasm'),
forceMode: 'production'
})
接著引入使用,因為現在還是必須非同步引入,所以我們偷懶直接這樣用,在引入後直接掛載到全域
const wasm = import('../wasm/pkg')
wasm
.then(m => {
console.log('load wasm success')
Vue.prototype.$wasm = m
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
})
.catch(e => {
console.log('wasm load error', e)
})
接著就可以在裡面使用了,來看一下兩個語言版本的速度差了多少吧,用先前使用的費式數列當作計算
const t1 = performance.now()
// fib(43)
this.$wasm.fib(43)
const t2 = performance.now()
console.log('time', (t2 - t1) / 1000)
在桌機板的 chrome
上,Javascript
大約耗時 5.2秒,wasm
大約耗時 3.2秒,速率大概提升了快 40%,根據這篇文章,在大部分的情況下,大約是提升 30% 速度,但這又會依據瀏覽器、裝置而有不同的差異,例如在 Firefox
就有可能提升將近 90%。
今天簡單介紹了 WebAssembly
,看起來執行速度很厲害,但會因此取代 Javascript
嗎?
我是覺得他會變成輔助 Javascript
的角色,當有需要複雜計算的時候就會使用 WebAssembly
的模組,雖然現在引用上還不是很方便,但希望哪天就可以像在 npm
安裝一樣,輕鬆的就可以使用了。而且最大的好處就是他可以從其他語言轉換,也就是說像是有古早積累的大公司可以直接轉換,不必因為要搬去 Web 而整個重改。現實生活中已經在使用的有 autocad、ebay,可以想像的是未來會有更多原本因為效能限制而無法在網頁環境出現的應用會因此而蓬勃發展,也許哪天,雖然掛著前端工程師,但是寫的不在是 Javascript
了呢。